﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
#if !WINDOWS_PHONE


#endif

namespace DARE
{
    /// <summary>
    /// Class use for manipulate a Model
    /// <example>
    /// <code>
    /// 
    ///     CScene cs = m_dare.SceneMgr.CreateScene("ModelSample");
    ///     CRender3D m_renderer = cs.CreateRender3D("ModelSampleRenderer");
    ///     
    ///     // Create a Model
    ///     CModel model = new CModel("DudeModel", "Dude");
    ///     
    ///     // Attach Model to the entity
    ///     m_renderer.CreateNode("Root").Entity.AttachModel(model);
    /// </code>
    /// </example>
    /// </summary>
    [AttrTypeDependentSerializable(typeof(DARE.Save.DataStructures.CModelData))]
    public class CModel : AModel
    {

        #region Field

        //private CAttachNode m_attach;
        private bool m_isInitialized = false;
        public const string MODELMINMAX = "MODELMINMAX";

        private CRasterizationContent m_state;

        private CMesh[] m_meshs;

        private CAnimationController m_animController;
        private int m_meshesCount;
        private bool _transparency = false;

        // added by Miwik to clone :D
        //private string m_modelPath;
        private Model m_modelModel;

        #endregion

        #region Property

        public DepthStencilState DepthStencilState
        {
            set
            {
                foreach (CMesh mesh in m_meshs)
                    mesh.DepthStencilState = value;
            }
        }

        ///// <summary>
        ///// Property used for serialization and loading
        ///// </summary>
        //[ContentSerializer]
        //internal CRasterizationContent State
        //{
        //    get { return this.m_state; }
        //    set
        //    {
        //        if (!value.State.Compare(this.m_state))
        //        {
        //            this.m_state = value;
        //            //UpdateMeshPartInOrderer();
        //        }
        //    }
        //}

        ///// <summary>
        ///// Property of Model's RasterizerState 
        ///// </summary>
        //[ContentSerializerIgnore]
        //public RasterizerState RasterizerState
        //{
        //    get { return this.m_state; }
        //    set
        //    {
        //        if (!value.Compare(this.m_state))
        //            this.State = new CRasterizationContent(value);
        //    }
        //}

        public override CEffect Effect
        {
            get { return this.m_effect; }
            set
            {
                if (this.m_effect != value)
                {
                    this.m_effect = value;
                    InitializeEffect();
                }
            }
        }

        private void InitializeEffect()
        {

        }

        public Matrix ModelWorld { get; private set; }

        [ContentSerializerIgnore]
        public CRender3D Renderer
        {
            get { return base.Node.Render3D; }
        }

        public CMesh[] Meshes
        {
            get { return this.m_meshs; }
        }

        public bool AsAnimation
        {
            get { return m_animController != null; }
        }

        // TODO : to remove
        public CAnimationController AnimationController
        {
            get { return this.m_animController; }
        }

        public bool ContainModel { get; private set; }

        public override string Name
        {
            get
            {
                return this.m_name;
            }
            set
            {
                if (this.m_name != value)
                    this.m_name = value;
            }
        }

        //public bool IsTransparence
        //{
        //    get { return this._transparency; }
        //    set
        //    {
        //        if (this._transparency != value)
        //        {
        //            this._transparency = value;
        //            for (int i = 0; i < this.m_meshesCount; ++i)
        //            {
        //                CMesh _mesh = this.m_meshs[i];
        //                for (int j = 0; j < _mesh.MeshPartCount; ++j)
        //                    _mesh.Parts[j].TransparencyModelChanged(!value);
        //            }
        //        }
        //    }
        //}

        public CMesh this[string name]
        {
            get
            {
                return (from el in m_meshs
                        where el.RealName == name
                        select el).SingleOrDefault();
            }
        }

        #endregion

        #region Event

        ///// <summary>
        ///// Event raise when the RasterizerState changed
        ///// </summary>
        //public event DataEvent<CModel, CRasterizationContent> RasterizerStateChanged;

        ///// <summary>
        ///// Event raised when the model changed
        ///// </summary>
        //public event DataEvent<IOrderedAndDrawable, string> ModelChanged;

        #endregion

        #region CTOR

        /// <summary>
        /// Used for serialization
        /// </summary>
        public CModel()
        {
            Initialize(null);
        }

        public CModel(string name, string modelPath)
            :this(name, modelPath, null)
        {

        }
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="modelPath">Content Pipeline path</param>
        /// <param name="render">renderer parent of this entity</param>
        public CModel(string name, string modelPath,RasterizerState state)
        {
            this.m_name = modelPath;
            m_model = new CLModel(modelPath);
            Initialize(state);
        }

        public CModel(string name, Model model)
            : this(name, model, null)
        {

        }

        public CModel(string name, Model model, RasterizerState state)
        {
            this.m_name = name;
            m_modelModel = model;

            m_model = new CLModel(model);

            Initialize(state);
        }

        // TODO
        public override AModel Clone()
        {
            CModel cloneModel = m_modelModel == null ? new CModel("plop" + CDare.Instance.IdCounterString, m_model.Path) :
                new CModel("plop" + CDare.Instance.IdCounterString, m_modelModel);

            cloneModel.Orientation = m_orientation;
            cloneModel.Scale = m_scale;
            cloneModel.Offset = m_offset;

            return cloneModel;
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="name">entity's name</param>
        /// <param name="render">renderer parent of this entity</param>
        //public CModel(string name, CRender3D render = null)
        //    : base(name, render)
        //{
        //    Initialize(render);
        //}

        #endregion

        #region Method

        protected override BoundingBox GetMinBoundingBox()
        {
            float maxValue = float.MinValue;
            foreach (CMesh mesh in m_meshs)
            {
                foreach (CMeshPart part in mesh.Parts)
                {
#if WINDOWS_PHONE
                    VertexPositionNormalTexture[] vertices = new VertexPositionNormalTexture[part.MeshPart.VertexBuffer.VertexCount];
                    part.MeshPart.VertexBuffer.GetData<VertexPositionNormalTexture>(vertices);
                    foreach (VertexPositionNormalTexture vertex in vertices)
#else
                    VertexPositionNormalTextureTangentBinormal[] vertices = new VertexPositionNormalTextureTangentBinormal[part.MeshPart.VertexBuffer.VertexCount];
                    part.MeshPart.VertexBuffer.GetData<VertexPositionNormalTextureTangentBinormal>(vertices);
                    foreach (VertexPositionNormalTextureTangentBinormal vertex in vertices)
#endif
                    {
                        if (Math.Abs(vertex.Position.X + Offset.X) > maxValue)
                            maxValue = Math.Abs(vertex.Position.X + Offset.X);
                        if (Math.Abs(vertex.Position.Y + Offset.Y) > maxValue)
                            maxValue = Math.Abs(vertex.Position.Y + Offset.Y);
                        if (Math.Abs(vertex.Position.Z + Offset.Z) > maxValue)
                            maxValue = Math.Abs(vertex.Position.Z + Offset.Z);
                    }
                }
            }
            return new BoundingBox(Vector3.One * -maxValue, Vector3.One * maxValue);
        }

        /// <summary>
        /// Initialized method
        /// </summary>
        /// <param name="render">renderer parent of this entity</param>
        public void Initialize(RasterizerState _state)
        {
            if (m_model.Data == null)
            {
                throw new Exception("There was a problem whith the initialization of " + m_name);
            }
            else if (m_model.Data.Tag != null)
            {
                CModelTag mt = (CModelTag) m_model.Data.Tag;
                if (mt.ContainsKey(CAnimationMgr.HIERARCHY))
                {
                    CHierarchy hierarchi = (CHierarchy) mt[CAnimationMgr.HIERARCHY];
                    this.m_animController = new CAnimationController(hierarchi); //this
                    //this.Effect.IsAnimationActive = true;
                }
                if (mt.ContainsKey(CModel.MODELMINMAX))
                {
                    m_maxBox = (BoundingBox) mt[CModel.MODELMINMAX];
                }
            }
            //if (_state == null)
            //    _state = new RasterizerState();
            //this.RasterizerState = _state;
            InitializeModel();
        }

        /// <summary>
        /// Method used for re-initialize model property
        /// </summary>
        private void ChangeModel(string old, string newPath)
        {
            if (!string.IsNullOrEmpty(old))
                CResourceMgr.UnLoad(old);
            if (!string.IsNullOrEmpty(newPath))
            {
                m_model.Path = newPath;
                InitializeModel();
            }
        }

        /// <summary>
        /// Method used for change the current model
        /// </summary>
        /// <param name="path"></param>
        public void ChangeModel(string path)
        {
            ChangeModel(m_model.Path, path);
        }

        //private void RemoveMeshModelFromRenderer()
        //{
        //    if (this.m_meshs != null)
        //    {
        //        for (int i = 0; i < this.m_meshesCount; ++i)
        //        {
        //            this.m_meshs[i].Unsubscribe();
        //        }
        //    }
        //}

        private void InitializeModel()
        {
            m_meshesCount = ((Model) m_model).Meshes.Count;
            Debug.Assert(m_meshesCount > 0);

            this.m_meshs = new CMesh[this.m_meshesCount];
            Matrix[] _transform = new Matrix[this.m_model.Data.Bones.Count];
            this.m_model.Data.CopyAbsoluteBoneTransformsTo(_transform);
            for (int i = 0; i < this.m_meshesCount; ++i)
            {
                ModelMesh mesh = m_model.Data.Meshes[i];
                this.m_meshs[i] = new CMesh(this, mesh, _transform[mesh.ParentBone.Index]);
                //this.m_meshs[i].();

                if (this.m_meshs[i].IsSkinned)
                    this.m_animController.AsSkinnedSkeleton = true;
            }
            m_isInitialized = true;
        }

        public CAnimConfig Play(string animName, EAnimState state)
        {
            return Play(animName, state, false);
        }

        public CAnimConfig Play(string animName, EAnimState state, bool priority)
        {
            var _result = m_animController.Play(animName, priority, state);
            if (_result == null)
                _result = m_animController.Play(this.m_model.Path.Replace('/', '-').Replace('\\', '-') + ':' + animName, priority, state);
            return _result;
        }

        public void AddBlender(IBlendController bc)
        {
            m_animController.AddBlender(bc);
        }

        public CSkeleton CreateSkeleton()
        {
            return CreateSkeleton(1.0f);
        }

        public CSkeleton CreateSkeleton(float scale)
        {
            return new CSkeleton(this, scale);
        }

        public List<string> GetAnimations()
        {
            List<string> _anims = new List<string>();

            //if (this.m_skeleton != null)
            //    return CAnimationMgr.ListOfPosibleAnimationBySkeleton(this.m_skeleton);
            return CAnimationMgr.ListOfPosibleAnimation(m_animController.m_hierarchy);
        }

        public override void Update(GameTime gameTime)
        {
            base.Update(gameTime);

            //this.m_attach.Update();
            this.ModelWorld = this.Relative * this.Node.WorldMatrix;
            if (this.m_animController != null)
            {
                this.m_animController.Update(gameTime, this.ModelWorld);
            }
            for (int i = 0; i < m_meshesCount; ++i)
            {
                CMesh _mesh = this.m_meshs[i];
                if (this.m_animController != null)
                    _mesh.UpdateRelative(this.m_animController);
                _mesh.UpdateHighlight(gameTime);
            }
            //base.IsInTheOrderer = true;
        }

        /// <summary>
        /// Attach the root of this Model to the Bone called _boneName of _model
        /// </summary>
        /// <param name="_model">Model bone's owner of the bone called _boneName</param>
        /// <param name="_boneName">this param is the name of the bone use to attach this model</param>
        /// <returns>false if this model is already attach by Node attach system or if the _model did'nt contains bone called
        /// _boneName</returns>

        //// TODO  
        //public bool AttachRootToBone(CModel _model, string _boneName)
        //{
        //    //if (this.m_attach.IsAttach)
        //    //    return false;
        //    //return ((CBonesController)this.m_animController).AttachToBone(_model, _boneName);
        //    return false;
        //}

        /// <summary>
        /// Detach the current node and attach its to the bone called _boneName from the _model skeleton
        /// </summary>
        /// <param name="_model">Model bone's owner of the bone called _boneName</param>
        /// <param name="_boneName">this param is the name of the bone use to attach this model</param>
        /// <returns>false if this model is already attach by the bone attach system
        /// or if the _model did'nt contains bone called _boneName</returns>
        public CAttach AttachNodeTo(CNode _node, string _boneName)
        {
            return this.AnimationController.AttachNodeTo(_node, _boneName);
            //if (this.AnimationController != null
            //    && this.m_animController.AsBoneAttachRoot)
            //    return false;

            //int index = _model.AnimationController.GetBoneIndex(_boneName);

            //if (index > -1)
            //{
            //    this.Node.Detach();
            //    this.Node.Position = Vector3.Zero;
            //    this.Node.Orientation = Quaternion.Identity;
            //    this.m_attach.AttachNodeToBone(this.Node, (CBonesController)_model.AnimationController, index);
            //    return true;
            //}

            //return false;
        }

        ///// <summary>
        ///// Detach this model from the skeleton if it was attach by the Node System 
        ///// </summary>
        //public void DetachNodeFromBone()
        //{
        //    this.m_attach.DetachNodeFromBone();

        //}

        ///// <summary>
        ///// Detach this model from the skeleton if it was attach by the bone System
        ///// </summary>
        //public void DetachBoneFromBone()
        //{
        //    //if (this.AnimationController != null
        //    //    && this.AnimationController is CBonesController
        //    //    && ((CBonesController)this.m_animController).AsBoneAttachRoot)
        //    //    ((CBonesController)this.m_animController).Detach();
        //}

        public void Detach(CNode _node)
        {
            this.m_animController.Detach(_node);
        }

        public void Detach(CAttach _attach)
        {
            this.m_animController.Detach(_attach);
        }

        /// <summary>
        /// Detach this model from all skeleton
        /// </summary>
        public void DetachAll()
        {
            this.m_animController.DetachAll();
        }

        public override void Draw(GameTime gameTime)
        {
            if (!Show || Node == null ||
                Node.Render3D == null || Node.Render3D.CameraMgr.ActiveCamera == null)
                return;

            base.Draw(gameTime);

            for (int i = 0; i < m_meshesCount; ++i)
            {
                CMesh _mesh = this.Meshes[i];
                if (_mesh.Drawable == false)
                    continue;
                for (int j = 0; j < _mesh.MeshPartCount; ++j)
                {
                    _mesh.Parts[j].Draw(gameTime);
                }
            }
        }

        internal override void AttachToRenderer()
        {
            if (!m_isInitialized)
                InitializeModel();
            base.AttachToRenderer();
        }

        internal override void DetachFromRenderer()
        {
            //RemoveMeshModelFromRenderer();
            base.DetachFromRenderer();
        }

        //private void UpdateMeshPartInOrderer()
        //{
        //    if (this.Meshes == null)
        //        return;
        //    foreach (var elem in this.Meshes)
        //    {
        //        for (int i = 0; i < elem.MeshPartCount; ++i)
        //        {
        //            elem.Parts[i].DataChanged();
        //        }
        //    }
        //}

        /// <summary>
        /// Return the CAnimConfig of the animation ask if is running
        /// </summary>
        /// <returns></returns>
        public CAnimConfig GetAnimConfig(string animationName)
        {
            return m_animController.GetAnimConfig(animationName);
        }

        public bool IsPlaying(string name)
        {
            return m_animController.IsPlaying(name) || m_animController.IsPlaying(this.m_model.Path.Replace('/', '-').Replace('\\', '-') + ':' + name);
        }

        public bool IsPlaying(CAnimConfig _config)
        {
            return IsPlaying(_config.AnimName);
        }

        #endregion

    }
}
